﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using HotelRegistrationSystem.Models;
using TextManipulation;

namespace HotelRegistrationSystem.DAL
{
    public static class HotelDAL
    {
        public static DbSet<HotelType> GetHotelTypes()
        {
            HotelRegistrationSystemEntities db = new HotelRegistrationSystemEntities();
            DbSet<HotelType> hotelTypes = db.HotelTypes;
            return hotelTypes;
        }

        public static List<HotelIndexPageModel> GetHotels(HotelSearchModel model = null)
        {
            // when no search input was given - create empty search model
            model = model ?? new HotelSearchModel(); 
            using (HotelRegistrationSystemEntities db = new HotelRegistrationSystemEntities())
            {
                if (model.AddressId != null)
                {
                   model.City = AddressDAL.GetAddress((int)model.AddressId).City; 
                }

                var result =
                    (from h in db.Hotels.Include("Rooms")
                     join a in db.Addresses on h.AddressId equals a.AddressId

                     // non-string search criteria will stay here to generate sql where clauses
                     let roomsCount = h.Rooms.Count()
                     let bedsCount = roomsCount == 0 ? 0 : h.Rooms.Sum(x => x.BedCount)

                     where model.RoomsCountMin == null ? true : roomsCount >= model.RoomsCountMin
                     where model.BedsCountMin == null ? true : bedsCount >= model.BedsCountMin
                     where model.StarsMin == null ? true : h.Stars >= model.StarsMin
                     where model.StarsMax == null ? true : h.Stars <= model.StarsMax

                     where model.ReservationTypeId == null ? true : h.ReservationTypeId == model.ReservationTypeId
                     where model.HotelTypeId == null ? true : h.HotelTypeId == model.HotelTypeId
                     
                     // Second level filtration 

                     where model.FacilityId == null ? true : h.Facilities.Where(x => x.FacilityId == model.FacilityId).Count() > 0                  
                     where model.RoomMinPrice == null ? true : h.Rooms.Where(x => x.Price >= model.RoomMinPrice).Count() > 0
                     where model.RoomMaxPrice == null ? true : h.Rooms.Where(x => x.Price <= model.RoomMaxPrice).Count() > 0
                     where model.HotelId == null ? true : h.HotelId == model.HotelId

                     // Second level filtration 
                     select new HotelIndexPageModel
                     {
                         HotelId = h.HotelId,
                         Name = h.Name,
                         Address = new AddressModel
                         {
                             AddressId = a.AddressId,
                             Region = a.Region,
                             PostCode = a.PostCode,
                             Country = a.Country,
                             City = a.City,
                             Street = a.Street
                         },
                         UserId = h.UserId,
                         PhoneNumber = h.PhoneNumber,
                         Email = h.Email,
                         Stars = h.Stars,
                         PictureName = db.HotelsRoomsPictures.Where(p => p.HotelId.Equals(h.HotelId)
                            && p.RoomId.Equals(null)).FirstOrDefault().PictureName,
                         RoomsCount = roomsCount,
                         BedsCount = bedsCount
                     }).AsEnumerable().MyFilter(model);

                return result.ToList();
            }
        }

        /// <summary>
        /// Slower in memory string filter
        /// </summary>
        private static IEnumerable<HotelIndexPageModel> MyFilter(
            this IEnumerable<HotelIndexPageModel> data, HotelSearchModel model)
        {
            Func<string, string, bool> MyCompare = (str1, str2) =>
            {
                // if search value is empty
                if (str1 == null || str1 == String.Empty || 
                    str2 == null || str2 == String.Empty)
                {
                    return true;
                }
                return str1.SmartCompare(str2);
            };

            return data
                .Where(x => MyCompare(x.Name, model.Name) &&
                            MyCompare(x.Address.Country, model.Country) &&
                            MyCompare(x.Address.Region, model.Region) &&
                            MyCompare(x.Address.City, model.City) &&
                            MyCompare(x.Address.Street, model.Street));
        }

        public static Hotel GetHotel(int id)
        {
            HotelRegistrationSystemEntities db = new HotelRegistrationSystemEntities();
            Hotel hotel = db.Hotels.Find(id);
            return hotel;
        }

        public static int AddHotel(HotelViewModel hotel, int userId)
        {
            int newHotelId = 0;
            using (HotelRegistrationSystemEntities db = new HotelRegistrationSystemEntities())
            {
                Hotel h = new Hotel
                {
                    Name = hotel.Name,
                    EarliestCheckIn = hotel.EarliestCheckIn,
                    LatestCheckOut = hotel.LatestCheckOut,
                    Address = new Address
                    {
                        Country = hotel.Address.Country,
                        Region = hotel.Address.Region,
                        City = hotel.Address.City,
                        Street = hotel.Address.Street,
                        PostCode = hotel.Address.PostCode
                    },
                    UserId = userId,
                    PhoneNumber = hotel.PhoneNumber,
                    Email = hotel.Email,
                    HotelTypeId = hotel.HotelTypeId,
                    Description = hotel.HotelDescription,
                    Earnest = hotel.Earnest,
                    ReservationDenyPeriod = hotel.ReservationDenyPeriod,
                    ContactPeriod = hotel.ContactPeriod,
                    ReservationTypeId = hotel.ReservationTypeId,
                    Stars = hotel.Stars
                };
                db.Hotels.Add(h);
                db.SaveChanges();
                newHotelId = h.HotelId;
            }

            return newHotelId;
        }

        public static void RemoveHotel(int hotelId)
        {
            using (HotelRegistrationSystemEntities db = new HotelRegistrationSystemEntities())
            {
                Hotel hotel = db.Hotels.Single(h => h.HotelId == hotelId);
                db.Hotels.Remove(hotel);
                db.SaveChanges();
            }
        }

        public static bool EditHotel(HotelViewModel hvm)
        {
            using (HotelRegistrationSystemEntities db = new HotelRegistrationSystemEntities())
            {
                Hotel hotel = db.Hotels.SingleOrDefault(h => h.HotelId == hvm.HotelId);
                if (hotel == null)
                {
                    return false;
                }
                hotel.Name = hvm.Name;
                hotel.EarliestCheckIn = hvm.EarliestCheckIn;
                hotel.LatestCheckOut = hvm.LatestCheckOut;
                hotel.PhoneNumber = hvm.PhoneNumber;
                hotel.Email = hvm.Email;
                hotel.Earnest = hvm.Earnest;
                hotel.ReservationDenyPeriod = hvm.ReservationDenyPeriod;
                hotel.ContactPeriod = hvm.ContactPeriod;
                hotel.HotelTypeId = hvm.HotelTypeId;
                hotel.ReservationTypeId = hvm.ReservationTypeId;
                hotel.Description = hvm.HotelDescription;

                hotel.Address.Country = hvm.Address.Country;
                hotel.Address.Region = hvm.Address.Region;
                hotel.Address.City = hvm.Address.City;
                hotel.Address.Street = hvm.Address.Street;
                hotel.Address.PostCode = hvm.Address.PostCode;
                hotel.Stars = hvm.Stars;

                db.Entry(hotel).State = EntityState.Modified;
                db.SaveChanges();
                return true;
            }
        }

        public static void AddImageToDB(string imageName, int hotelId, int? roomId = null)
        {
            using (HotelRegistrationSystemEntities db = new HotelRegistrationSystemEntities())
            {
                db.HotelsRoomsPictures.Add(new HotelsRoomsPicture
                {
                    HotelId = hotelId,
                    RoomId = roomId,
                    PictureName = imageName
                });
                db.SaveChanges();
                db.Dispose();
            }
        }

        public static HotelsRoomsPicture GetImage(int hotelId, int? roomId = null)
        {
            using (HotelRegistrationSystemEntities db = new HotelRegistrationSystemEntities())
            {
                return db.HotelsRoomsPictures.Where(p => p.HotelId.Equals(hotelId)
                    && p.RoomId.Equals(roomId)).FirstOrDefault();
            }
        }

        public static void AddRoom(RoomHotelModel room)
        {
            HotelRegistrationSystemEntities db = new HotelRegistrationSystemEntities();
            db.Rooms.Add(new Room()
            {
                HotelId = room.HotelId,
                Name = room.Name,
                Price = room.Price,
                Description = room.Desciption,
                RoomTypeId = room.RoomTypeId,
                BedCount = room.BedCount
            });

            db.SaveChanges();
            db.Dispose();
        }

        public static void EditRoom(RoomHotelModel roomModel)
        {
            HotelRegistrationSystemEntities db = new HotelRegistrationSystemEntities();

            Room room = db.Rooms.Single(a => a.RoomId == roomModel.RoomId);

            room.Name = roomModel.Name;
            room.Price = roomModel.Price;
            room.Description = roomModel.Desciption;
            room.RoomTypeId = roomModel.RoomTypeId;
            room.BedCount = roomModel.BedCount;

            db.Entry(room).State = EntityState.Modified;
            db.SaveChanges();
        }

        public static RoomHotelModel GetRoomById(int roomId)
        {
            HotelRegistrationSystemEntities db = new HotelRegistrationSystemEntities();

            Room room = db.Rooms.Single(a => a.RoomId == roomId);

            return new RoomHotelModel()
            {
                RoomId = room.RoomId,
                HotelId = room.HotelId,
                Name = room.Name,
                Price = room.Price,
                Desciption = room.Description,
                RoomType = new RoomTypeModel()
                {
                    RoomTypeId = room.RoomType.RoomTypeId,
                    Type = room.RoomType.Type,
                    ReserveWhole = room.RoomType.ReserveWhole
                },
                BedCount = room.BedCount
            };
        }

        public static void RemoveRoom(int roomId)
        {
            using (HotelRegistrationSystemEntities db = new HotelRegistrationSystemEntities())
            {
                Room room = db.Rooms.Single(h => h.RoomId == roomId);
                db.Rooms.Remove(room);
                db.SaveChanges();
            }
        }

        public static List<Room> GetRoomsByHotelId(int hotelId)
        {
            return new HotelRegistrationSystemEntities().Rooms.Where(a => a.HotelId == hotelId).ToList();
        }
               
        public static List<Facility> GetHotelFacilities()
        {
            using (HotelRegistrationSystemEntities db = new HotelRegistrationSystemEntities())
            {
                return db.Facilities.ToList();
            }
        }

        public static List<ReservationType> GetReservationTypes()
        {
            using (HotelRegistrationSystemEntities db = new HotelRegistrationSystemEntities())
            {
                return db.ReservationTypes.ToList();
            }
        }

        public static List<HotelsRating> GetHotelsRatings()
        {
            using (HotelRegistrationSystemEntities db = new HotelRegistrationSystemEntities())
            {
                return db.HotelsRatings.ToList();
            }
        }

        #region Hotel rating
        public static int GetHotelRating(int hotelId)
        {
            using (var context = new HotelRegistrationSystemEntities())
            {
                if (context.HotelsRatings.Any(h => h.HotelId == hotelId))
                {
                    return (int)Math.Round(((double)(context.HotelsRatings.Where(h => h.HotelId == hotelId).Sum(h => h.Rating)) /
                        context.HotelsRatings.Count(h => h.HotelId == hotelId)));
                }

                return 5;
            }
        }

        public static int EvalHotel(int hotelId, int mark, string username)
        {
            var userId = AccountDAL.GetUser(username).UserId;
            using (var context = new HotelRegistrationSystemEntities())
            {
                var hotelRatingRecord = new HotelsRating
                {
                    HotelId = hotelId,
                    UserId = userId,
                    Rating = mark,
                };
                context.HotelsRatings.Add(hotelRatingRecord);
                context.SaveChanges();
            }

            return GetHotelRating(hotelId);
        }

        public static bool CanEvaluateHotel(int hotelId, string username)
        {
            var user = AccountDAL.GetUser(username);
            using (var context = new HotelRegistrationSystemEntities())
            {
                // Check user has not already evaluated the hotel services
                if (context.HotelsRatings.Any(h => h.HotelId == hotelId && h.UserId == user.UserId))
                    return false;

                // Check user is not an owner of the hotel
                if (context.Hotels.Any(h => h.HotelId == hotelId && h.UserId == user.UserId))
                    return false;

                // Check user has had experience with hotel services
                foreach (var resRoom in context.ReservationsRooms)
                {
                    if (resRoom.Room.HotelId == hotelId && resRoom.Reservation.UserId == user.UserId)
                        return true;
                }
            }

            return false;
        }
        #endregion
    }
}